/*
 *  Copyright (C) 2010 Glauco
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.swing.components.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 *
 * @author Glauco
 */
public class FocusTraversal implements FocusTraversalListener{

    private Component component;

    private ActionListener action;

    private boolean onlyWhenCycleDown;

    private boolean locked;

    private Container topContainer;

    public FocusTraversal(Component component, ActionListener action) {
        this.component = component;
        this.action = action;
        onlyWhenCycleDown = true;
        locked = false;
    }

    public FocusTraversal(Component component, ActionListener action,
            Container topContainer) {
        this(component,action);
        this.topContainer = topContainer;
    }

    public ActionListener getAction() {
        return action;
    }

    public void setAction(ActionListener action) {
        this.action = action;
    }

    public Component getComponent() {
        return component;
    }

    public void setComponent(Component component) {
        this.component = component;
    }

    public Container getContainer() {
        Container c = component.getFocusCycleRootAncestor();
        if (c != null)
            return c;

        return component.getParent().getFocusCycleRootAncestor();
    }

    public boolean isOnlyWhenCycleDown() {
        return onlyWhenCycleDown;
    }

    public void setOnlyWhenCycleDown(boolean onlyWhenCycleDown) {
        this.onlyWhenCycleDown = onlyWhenCycleDown;
    }

    public void register(){
        FocusManager.getInstance().addFocusTraversal(this);
    }

    public void unRegister(){
        FocusManager.getInstance().removeFocusTraversal(this);
    }

    public Container getTopContainer() {
        return topContainer;
    }

    public void setTopContainer(Container topContainer) {
        this.topContainer = topContainer;
    }

    public void checkFocusTraversal(Container activeContainer,
            FocusTraversalPolicy ftp, Component oldComponent,
            Component newComponent, Component lastComponet){

        // retorna se os componentes forem de janelas diferentes
        // ou o container ativo for nulo ou não ter focusTravelPolice
        if(oldComponent.getFocusCycleRootAncestor() !=
                newComponent.getFocusCycleRootAncestor() || ftp == null){
            return ;
        }

        if(!checkTopContainer(oldComponent, newComponent))
            return ;

        try{
            lock();
        }catch(Exception ex){
            return;
        }

        // flag que determina se o focus foi para baixo
        boolean cycleDown = false;
        // flag que deremina que passou pelo componente
        boolean traversal = false;

        Component actualComponent = oldComponent;

        // percorre o componente atual, que era onde o focus estava,
        // até o componente onde o focus está,
        // No caso do focus atual estar em um componente anterior ao componente
        // onde o focus estava, percorre até o último camponente da janela,
        while(!actualComponent.equals(newComponent) &&
                !actualComponent.equals(lastComponet)){


            if(component == actualComponent){
                traversal = true;
            }

            try{
                actualComponent = ftp.getComponentAfter(activeContainer,
                        actualComponent);

                // se chegar no novo componente é porque o focus foi para baixo
                if(actualComponent.equals(newComponent))
                    cycleDown = true;
            }catch(Exception ex){
                break;
            }
        }

        if (traversal && (cycleDown || !onlyWhenCycleDown))
            this.getAction().actionPerformed(new ActionEvent(
                        component,
                        ActionEvent.ACTION_PERFORMED,
                        "focusTraversal"));

        unLock();
    }

    private synchronized void lock() throws Exception{
        if(locked)
            throw new Exception("Is Locked!");
        locked = true;
    }

    private synchronized void unLock(){
        locked = false;
    }

    /**
     * Verifica se os componentes tem como pai o topContainer.
     * Caso o topContainer for nulo, ou seja, não foi definido nenhum Container
     * como topContainer para o FocusTraversal, retorna sempre verdadeiro.
     *
     * @param oldValue objeto para verificar.
     * @param newValue objeto para verificar.
     * @return verdadeiro se os objetos pertencem ao mesmo topContainer ou o
     * topContainer seja nulo, falso caso contrario.
     */
    private boolean checkTopContainer(Component oldValue, Component newValue){

        if(topContainer == null)
            return true;

        if(checkTopContainer(oldValue.getParent()) &&
                checkTopContainer(newValue.getParent()))
            return true;

        return false;
    }

    /**
     * Verifica se um container é igual ao topContainer ou tem como pai o
     * topContainer.
     *
     * @param otherContainer container para comparar com o topContainer.
     * @return true se o otherContainer for igual ou ter como pai o topContainer.
     */
    private boolean checkTopContainer(Container otherContainer){

        if (otherContainer == null)
            return false;

        if (otherContainer == topContainer)
            return true;

        return checkTopContainer(otherContainer.getParent());
    }
}
